因为正则表达式的在文本处理,字符串匹配中有着重要应用,因此本文对其基本语法规则及在Python中的应用进行了简要介绍。
注:本文大部分是对博文正则表达式30分钟入门教程,LiZeC的正则表达式笔记以及python re库官方文档的归纳和整理,更详细内容可查阅原文。
基本语法
- 基本概念: 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来匹配、检测、替换特定的字符串等。
字符
- 普通字符:通常正则表达式中出现的任意一个字符代表匹配和他们一样的字符。
- 特殊字符:特殊字符并不匹配他们本身,而是有特殊含义的,具体有:
. ^ $ * + ? { } [ ] \ | ( )
若要匹配这些字符本身,则需要加上\进行转义(escape),如\*表示匹配*本身。
特殊字符含义
| 字符 | 含义 |
|---|---|
| . | 匹配任意单个字符(除换行符“\n”外) |
| \w | 匹配任意字母、数字、下划线、汉字(即匹配普通字符) |
| \s | 匹配任意的空白符(空格、制表符(Tab)、换行符、中文全角空格等) |
| \d | 匹配数字(0-9) |
| \b | 匹配单词的开始或结束(单词边界),即非字母数字的任意其他字符 |
| ^ | 匹配字符串的开始 |
| $ | 匹配字符串的结束 |
对单词边界
\b:如正则表达式\blove\b,就会匹配句子”I love you”中的love, 而不会匹配”I aloveb you”中的love。一些字符的反义表示
如\w,\b这些特殊字符的大写表示相反的含义,具体如下:
| 字符 | 含义 |
|---|---|
| \W | 匹配不是字母、数字、下划线、汉字的字符(多用来匹配特殊字符) |
| \S | 匹配任意不是空白符的字符 |
| \D | 匹配任意非数字的字符 |
| \B | 匹配不是单词开头或结束的位置 |
重复匹配限定符
以下字符称为“限定符”(限制次数的符号),跟在特殊字符的后面,表示重复该字符特定次数。
如\d+匹配1个或更多个的数字。
| 限定符 | 含义 |
|---|---|
| * | 重复\(\geq\)0次 |
| + | 重复\(\geq\)1次 |
| ? | 重复0次或1次 |
| {n} | 重复n次 |
| {n,} | 重复\(\geq\)n次 |
| {n,m} | 重复n到m次 |
贪婪、懒惰匹配原则
- 正则表达式的匹配原则为贪婪匹配:即在满足条件的情况下,匹配尽可能多的字符。
eg.a.*b会匹配以a开始,以b结束的最长的字符串。如果用来搜索aabab,则会匹配整个字符串aabab而非aab。 - 若需要懒惰匹配:即匹配尽可能少的字符,就在相应的重复字符后加
?即可。
eg.a.*?b匹配以a开始,以b结束的最短的字符串,搜索aabab会匹配到aab和ab两个子串(之所以没有仅仅匹配到ab子串,是因为正则表达式的匹配还会考虑开始的先后顺序,最开始匹配的优先级最高)。 - 懒惰匹配限定符如下:
| 限定符 | 含义 |
|---|---|
| *? | 重复\(\geq\)0次,但尽可能少重复 |
| +? | 重复\(\geq\)1次,但尽可能少重复 |
| ?? | 重复0次或1次,但尽可能少重复 |
| {n,}? | 重复\(\geq\)n次,但尽可能少重复 |
| {n,m}? | 重复n到m次,但尽可能少重复 |
字符类
- 在
[和]中的若干字符构成一个字符类(character class)。 - 字符类是为了匹配某种字符集合,表示此位置可以匹配这个类中的任意一个字符。
- 整个字符类所起到的作用和普通字符相同,都是只匹配单个字符,因此字符类可作为整体再接受其他限定,如
? + {n,m}等等。 - 可以使用
-来表示一个范围,例如[a-c]表示[abc] - 在字符类中的特殊符号不被转义
- 反向匹配:在字符类中,如果以^开头,则表示匹配除此字符类中提及的任何其他字符。如
[^5]匹配任何不是5的字符。
分枝条件
- 在正则表达式表示“或”的逻辑,两个条件用
|连接即可。
eg.\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。 - 在使用分枝条件时要注意各个条件的顺序,如果上文改成
\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。因为一旦前面的分枝满足的话就不会再管其他条件了。
注释
不推荐在正则表达式内部写注视,注释推荐写在正则表达式的外部语言中。
正则表达式高级特性
分组
- 上文提到了如何重复单个字符(直接在字符后面加表示重复的限定符即可),但若想重复多个字符,就需要用到分组的概念。
- 用小括号
()来指定分组(也叫子表达式),之后就可以对这个分组的整体进行重复或其他操作。
eg.(\d{1,3}\.){3}\d{1,3},就是一个比较粗糙的IP地址匹配式。
后向引用
- 使用小括号指定一个分组后,这个分组可以作为一个整体在后文中作进一步处理。
- 默认情况下,每个组会有一个组号,从左到右,第一个出现的分组组号为1(注意不是0),第二个为2,以此类推。
- 如何引用:在后问中使用
\+组号的形式来引用,如\1表示引用1号分组的。
eg.\b(\w+)\b\s+\1\b可以来匹配如go go 或kitty kitty 这种连着两个重复单词。
零宽断言
- 用于查找在某些内容前面或后面的东西(但不包括这些内容),类似于
^ $ \b这种占位符,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此被称为零宽断言。 (?=exp):用于匹配exp前面出现的表达式。
e.g\b\w+(?=ing\b)用于匹配以ing为结尾的单词的前面部分(不包括ing)(?<=exp):用于匹配exp后面出现的表达式。
e.g(?<=\bre)\w+\b用于匹配以re开头的单词的后面部分(不包括re)- 更多示例:
(?<=\s)\d+(?=\s):匹配以空白符间隔的数字(不包括这些空白符)((?<=\d)\d{3})+\b:要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分(用它对1234567890进行查找时结果是234567890)
负向零宽断言
- 用于确保某个模式不会出现。与字符类
[^exp]的区别:虽然不匹配这个字符,但字符类总是会匹配某个字符的,这会限制字符类的应用场景;而负向零宽断言不匹配字符,其只指代一个位置。
eg. 如果用\b\w*q[^u]\w*\b来匹配“出现了字母q,但是q后面跟的不是字母u”的单词,则像“Iraq, Benq”这种q直接作为最后一个字符的情况就会出错(字符类总要匹配一个字符),因此就需要负向零宽断言。 (?!exp):断言此位置的后面不能匹配表达式exp。
eg.\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词(?<!exp):断言此位置的前面不能匹配表达式exp。
eg.(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
递归匹配
- 用于匹配一些嵌套的层次结构,如
(100*(50+15)),如果只是简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容。假如原来的字符串里的左括号和右括号出现的次数不相等,比如(5/(3+2))),那我们的匹配结果里两者的个数也不会相等。如果想要想匹配到最长的,而且配对正确的字符串,就需要用到递归匹配。 - 具体内容参见博文正则表达式30分钟入门教程
经典正则表达式实例
| 正则表达式 | 解释 |
|---|---|
^[A-Za-z]+$ |
由26个字母组成的字符串 |
^[A-Za-z0-9]+$ |
由26个字母和数字组成的字符串 |
^-?\d+$ |
整数形式的字符串 |
^[0-9]*[1-9][0-9]*$ |
正整数形式的字符串 |
[\u4e00-\u9fa5] |
判断是不是中文字符 |
在Python中使用正则表达式
使用原生字符串
- 正则表达式中使用
\n表示转义,而python中恰好也使用\n表示转义,因此在python中使用正则表达式则需要\\来表示反斜杠。 - 为了节省过多的反斜杠,可以使用Python原生字符串特性,即在字符串开头加上
r,如r\d,在这个字符串中每个字符表示其本身,Python不进行转义。
re库
在Python中使用正则表达式直接导入re库即可 import re。
常用函数介绍
| 函数 | 功能 |
|---|---|
| re.compile() | 编译正则表达式,返回Regular Expression Objects对象 |
| re.search() | 在字符串中查找匹配的子串第一次出现的位置,匹配成功返回match对象否则返回None |
| re.findall() | 在字符串中查找所有满足条件的子串,返回string list |
| re.match() | 强制从起始位置开始匹配,匹配成功返回match对象否则返回 None |
| re.split() | 将正则表达式作为separator来分割字符串,返回string list |
| re.sub() | 在字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
| re.escape() | 自动在字符串中添加 “\” 转义符(除去字母数字下划线),返回修改后的字符串 可用来自动生成包含特殊字符的正则表达式 |
参数说明:
- re.compile(pattern, flags=0)
pattern: 表示正则表达式的字符串
flags: 正则表达式的控制标记 - re.search(pattern, string, flags=0)
pattern:同上
string:待查找的字符串
flags: 同上 - re.findall(pattern, string, flags=0)
参数同上 - re.match(pattern, string, flags=0)
参数同上
re.fullmatch(pattern, string, flags=0)
参数同上,只不过是查找整个字符串 - re.sub(pattern, repl, string, count=0, flags=0)
repl: 用来替代匹配到的子串的字符串 - re.escape(pattern)
eg.pattern1 = re.escape('python.exe')),pattern1就为”python\.exe”,可直接作为pattern参数传入其他函数中,用来匹配“python.exe”。当pattern含有大量特殊字符时使用这个函数就很方便。
str.replace()和re.sub的比较
- str.replace(old, new[, count])是字符串的替代函数,其中new为替换的字符串,old为待替换的字符串,old只能为substring而不能为字符串,因此替换功能较为简单。
- re.sub(pattern, repl, string, count=0, flags=0)则使用了正则表达式,可进行更复杂的替换操作,但同时开销也更大。
- 因此能用replace()尽量用,复杂的替换操作再使用正则表达式。
控制标记flag介绍
| 标记名(简写/全称) | 含义 |
|---|---|
| re.A / re.ASCII | 使\w,\b,\s和\d只匹配ASCII字符 eg. 不会匹配汉字和其他Unicode字符 |
| re.I / re.IGNORECASE | 忽略正则表达式大小写 |
| re.M / re.MULTILINE | 使得^表示每一行的开始,$表示每一行的结束 原义仅表示一个单词的开始和结束 |
| re.S / re.DOTALL | 使得.可以匹配\n字符 |
| re.X / re.VERBOSE | 忽略正则表达式内部的空白符 |
注:这些变量在VSCode的python下没有提示,但可以运行。
正则表达式的两种使用方法
直接函数调用:result = re.search(pattern, string)
先编译后使用:
prog = re.compile(pattern)
result = prog.search(string)两种方式效果相同,且因为缓存机制,在正则表达式数量较少的的情况下,两者效率也相近;但如果重复地调用很多正则表达式,先编译好的效率会更高。
Match类
re.search()和re.match()两个函数的返回对象,包含了匹配的相关信息,常用函数和属性如下:
| 名称 | 说明 |
|---|---|
| match.group() | 返回特定的分组,string或tuple的形式 |
| match.groups() | 以tuple的形式返回所有分组 |
| match.start() | 返回特定分组的起始地址 |
| match.end() | 返回特定分组的结束地址 (会比实际大1位,因为python字符串截取前闭后开的特性) |
| match.re | 为编译好的regular expression object对象 |
| match.string | 为传入到re.search()或re.match()的待匹配字符串 |
参数说明:
- match.group([group1, …])
group1为组号,默认为0(这时返回the whole match);当没有参数或只有一个参数时返回string,当有2个或以上参数时返回tuple。 - match.groups(default=None)
default为没有匹配到的分组指定所显示的名字,通常保持默认即可。 - match.start([group])、match.end([group])
两个函数中group都是指组号,返回特定组号的起始、终止地址;
因此相应分组的子串可通过m.string[m.start(g):m.end(g)]来获取。
Post Date: 2019-02-07
版权声明: 本文为原创文章,转载请注明出处